/*
 * Copyright 2013, NuzoweR@Schneefeuer <nuzower@gmail.com>.
 * The Settlers Online - Combat Simulator (tsosim).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Debug.h"
#include "SimParser.h"
#include "Units.h"
#include "Garrison.h"
#include "Utils.h"
#include "Simulator.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <list>


SimParser::SimParser()
{}

bool SimParser::process_import(std::string const& LINE, int start_pos, int line_number)
{
	std::string filename;
	if (!utils::extract_path_string(LINE, start_pos, filename))
	{
		debug::ERROR(*Simulator::get_ostream(), line_number, "could not read filename in '" + LINE + "'");
		return false;
	}

	bool ok=false;


	std::string path;
	if(!path_stack.empty())
		path = path_stack.front();

	std::string dir, file;
	utils::path_get_dir_file(filename, dir, file);

	if(!dir.empty()) {
		path += "/" + dir;
	}
	//path += "/" + file;

	path_stack.push_front(path);
	ok = process_file(file);
	path_stack.pop_front();

	if(!ok) {
		return false;
	}

	return true;
}

bool SimParser::process_file(std::string const& filename)
{
	std::ifstream FILE;

	std::ostream& OUT = *Simulator::get_ostream();

	debug::DEBUG(OUT, 0, "processing file '"+ filename + "'");

	std::string current_path;
	if(!path_stack.empty()) 
		current_path = path_stack.front();

	{
		// try to open file as it is
		FILE.open(filename.c_str());

		if( !FILE.is_open() ) {

			// opening file failed, now try to prepend a stored path

			if(path_stack.empty() || (!path_stack.empty() && path_stack.front().empty()))
			{
				debug::ERROR(OUT, 0, "Could not open file : " + filename);
				return false;
			}

			std::string path = path_stack.front();

			std::string dir, file;
			utils::path_get_dir_file(filename, dir, file);

			std::string full_path = path;
			if(!dir.empty()) {
				full_path += "/" + dir;
			}
			full_path += "/" + file;

			FILE.open(full_path.c_str());

			if(!FILE.is_open())
			{
				debug::ERROR(OUT, 0, "Could not open file : " + full_path);
				return false;
			}
		}
	}

	Mode current_mode = MODE_NORMAL;

	int line_number = 0;
	std::string LINE;
	bool getl = false;
	bool last_line = false;
	while( (getl=std::getline(FILE, LINE).good()) || !last_line )
	{
		line_number++;

		if(line_number == 1)
		{
			utils::check_utf8_bom(LINE, true);
		}
		if(!getl)
		{
			last_line = true;
		}

		utils::Pos_t line_end = LINE.length();

		if(line_end == 0) continue; // empty line

		// find first not empty character
		utils::Pos_t start = LINE.find_first_not_of(" \t", 0);

		if( start == std::string::npos ) continue; // only white chars

		// check for comment line
		if( (start + 2) <= line_end )
		{
			if(LINE.at(start) == '-' && LINE.at(start+1) == '-') continue; // comment line -> ignore it
		}

		// check if current mode has changed
		if(start < line_end && LINE.at(start) == '#')
		{
			// when a line starts with #, then a new command is about to be processed
			// if the last command was a #sim command, then the simulation will be executed now
			set_current_sim(NULL);

			if(LINE.find("#usepath", start) != std::string::npos) {
				std::string path;
				if(!utils::extract_path_string(LINE, 0, path))
				{
					debug::ERROR(OUT, line_number, "could not read path in '" + LINE + "'");
					return false;
				}
				if(path.empty()) {
					debug::ERROR(OUT, line_number, "path is empty in '" + LINE + "'");
					return false;
				}
				path_stack.push_front(path);
				current_mode = MODE_NORMAL;
				continue;
			}
			else if(LINE.find("#debug", start) != std::string::npos) {
				std::string dummy, debug;
				if(!utils::process_line(LINE, start, dummy, debug))
				{
					debug::ERROR(OUT, line_number, "could not read debug level in - '"+ LINE +"'");
					return false;
				}
				if(!debug::SetLevel(debug))
				{
					debug::ERROR(OUT, line_number, "unknown debug level - " + debug);
					return false;
				}
				continue;
			}
			else if(LINE.find("#import", start) != std::string::npos) {

				if(!process_import(LINE, start, line_number))
				{
					debug::ERROR(OUT, line_number, "import error in file '" + filename + "'");
					FILE.close();
					return false;
				}
				current_mode = MODE_NORMAL;
				debug::DEBUG(OUT, line_number, "processing file '"+ filename + "'");
				continue;
			}
			else if(LINE.find("#unit", start) != std::string::npos) {
				current_mode = MODE_UNIT;
			}
			else if(LINE.find("#garrison", start) != std::string::npos) {
				current_mode = MODE_GARRISON;
			}
			else if(LINE.find("#sim", start) != std::string::npos) {
				current_mode = MODE_SIM;
			}
			else if(LINE.find("#exit", start) != std::string::npos) {
				break;
			}

		}

		if(current_mode == MODE_UNIT) {
			if( process_unit(LINE, start, line_number, filename) == false )
			{
				debug::ERROR (OUT, line_number, "error during unit processing in file '" + filename + "'");
				return false;
			}
		} else if(current_mode == MODE_GARRISON) {
			if( process_garrison(LINE, start, line_number, filename) == false )
			{
				debug::ERROR (OUT, line_number, "error during garrison processing in file '" + filename + "'");
				return false;
			}
		} else if(current_mode == MODE_SIM) {
			if( process_sim(LINE, start, line_number, filename) == false )
			{
				debug::ERROR (OUT, line_number, "error during simulation processing in file '" + filename + "'");
				return false;
			}
		} else {
			debug::WARNING(OUT, line_number, "cannot process line - " + LINE );
			debug::WARNING(OUT, line_number, "  in file '" + filename + "'" );
		}
	}

	FILE.close();

	// make sure a #sim command at the end of a file is executed
	set_current_sim(NULL);

	while(!path_stack.empty() && path_stack.front() != current_path) {
		path_stack.pop_front();
	}
	
	return true;
}


bool SimParser::process_unit(std::string const& LINE, int start_pos, int line_number, std::string const& filename)
{
	/*
	 *  syntax :
	 *    #unit : unit_id [, de="unit_screen_name"]
	 *      hitpoints : 10
	 *      damage    : 20,40
	 *      skills    : FIRST_STRIKE | LAST_STRIKE | ATTACK_WEAKEST | SPLASH_DAMAGE | TOWER_BONUS
	 *      attack_id : 123
	 *
	 *    #unit : unit_id [, unit_screen_name]
	 *      ...
	 */

	std::ostream& OUT = *Simulator::get_ostream();

	std::string front, back;

	if(LINE.at(start_pos) == '#')
	{
		std::string dummy, names;
		utils::process_line(LINE, start_pos, dummy, names);

		std::string name, name_id;

		std::list<std::string> namelist;
		int num = utils::extract_list_from_string(names, namelist);
		if(num > 2) {
			debug::ERROR(OUT, line_number, "too many names");
		}
		if(num > 0) name_id = namelist.front();
		if(num > 1) name    = namelist.back();
		else        name    = name_id;

		CURRENT.unit = Unit::get(name_id, true);

		std::string error;

		if(CURRENT.unit != NULL) 
		{
			std::string oldname = CURRENT.unit->name;

			Unit::set_name(name_id, name, error);
			CURRENT.unit_new = false;

			debug::INFO(OUT, line_number, "new unitname - " + name_id + " [" + oldname + " -> " + name + "]");
		}
		else
		{
			CURRENT.unit = Unit::create(name_id, name, error);

			if(CURRENT.unit == NULL) {
				debug::ERROR(OUT, line_number, error);
				return false;
			}

			CURRENT.unit_new = true;

			debug::INFO(OUT, line_number, "new unit - " + name_id + " [" + name + "]");
		}
	}
	else
	{
		if(utils::process_line(LINE, start_pos, front, back))
		{
			if(!CURRENT.unit_new) {
				debug::WARNING(OUT, line_number, "resetting value for unit '" + CURRENT.unit->name_id + "' - " + LINE);
				debug::WARNING(OUT, line_number, "  in file '" + filename + "'");
			}
		}

		if(front == "hitpoints") {
			std::list<int> hit;
			int num = utils::extract_list_from_string(back, hit);
			if(num != 1) {
				debug::ERROR(OUT, line_number, "unexpected hitpoints value - " + back);
				return false;
			}
			CURRENT.unit->hitpoints = hit.front();
		} 
		else if(front == "damage") {
			std::list<int> damage;
			int num = utils::extract_list_from_string(back, damage);
			if(num != 2) {
				debug::ERROR(OUT, line_number, "expected unit damage as tuple");
				return false;
			}
			CURRENT.unit->min_damage = damage.front();
			CURRENT.unit->max_damage = damage.back();

			if(CURRENT.unit->min_damage < 0)
			{
				debug::ERROR(OUT, line_number, "expected non-negative minimum damage");
				return false;
			}
			if(CURRENT.unit->max_damage < CURRENT.unit->min_damage)
			{
				debug::ERROR(OUT, line_number, "maximum damage smaller than minimum damage");
				return false;
			}

		}
		else if(front == "accuracy") {
			std::list<int> acc;
			int num = utils::extract_list_from_string(back, acc);
			if(num != 1) {
				debug::ERROR(OUT, line_number, "unexpected accuracy value - " + back);
				return false;
			}
			CURRENT.unit->accuracy = acc.front();
		}
		else if(front == "attack_id") {
			std::list<int> acc;
			int num = utils::extract_list_from_string(back, acc);
			if(num != 1) {
				debug::ERROR(OUT, line_number, "unexpected attack_id value - " + back);
				return false;
			}
			CURRENT.unit->attack_id = acc.front();
		}
		else if(front == "skills") {
			std::list<std::string> skills;
			int num = utils::extract_list_from_string(back, skills);
			if(num == 0) {
				debug::WARNING(OUT, line_number, "empty skill list");
				debug::WARNING(OUT, line_number, "  in file '" + filename + "'");
				return true;
			}
			std::list<std::string>::iterator iter = skills.begin();
			for(; iter != skills.end(); ++iter)
			{
				//if     (*iter == "NONE") {
				//	current_unit->skills &= Skills::NONE;
				//} else 
				if(*iter == "FIRST_STRIKE") {
					if(CURRENT.unit->initiative == Unit::I_NORMAL) {
						CURRENT.unit->initiative = Unit::I_FIRST;
					} else {
						std::string initiative = 
							CURRENT.unit->initiative == Unit::I_FIRST ? "FIRST_STRIKE" :
							CURRENT.unit->initiative == Unit::I_LAST  ? "LAST_STRIKE" :
							CURRENT.unit->initiative == Unit::I_NORMAL? "NORMAL" : "INVALID"; // this should never happen
						debug::ERROR(OUT, line_number, "unit initiative already set to " + initiative);
					}
				}
				else if(*iter == "LAST_STRIKE") {
					if(CURRENT.unit->initiative == Unit::I_NORMAL) {
						CURRENT.unit->initiative = Unit::I_LAST;
					} else {
						std::string initiative = 
							CURRENT.unit->initiative == Unit::I_FIRST ? "FIRST_STRIKE" :
							CURRENT.unit->initiative == Unit::I_LAST  ? "LAST_STRIKE" :
							CURRENT.unit->initiative == Unit::I_NORMAL? "NORMAL" : "INVALID"; // this should never happen
						debug::ERROR(OUT, line_number, "unit initiative already set to " + initiative);
					}

				}				
				else if(*iter == "ATTACK_WEAKEST") 
					CURRENT.unit->skills = CURRENT.unit->skills | Unit::S_ATTACK_WEAKEST;
				else if(*iter == "SPLASH_DAMAGE")  
					CURRENT.unit->skills = CURRENT.unit->skills | Unit::S_SPLASH_DAMAGE;
				else if(*iter == "TOWER_BONUS")    
					CURRENT.unit->skills = CURRENT.unit->skills | Unit::S_TOWER_BONUS;
				else if(*iter == "GENERAL")    
					CURRENT.unit->skills = CURRENT.unit->skills | Unit::S_GENERAL;
				else {
					debug::ERROR(OUT, line_number, "unknown skill - " + (*iter));
					return false;
				}
			}
		}
		else {
			debug::ERROR(OUT, line_number, "unknown argument (" + front + ") during unit processing");
			return false;
		}
	}
	return true;
}

bool SimParser::process_garrison(std::string const& LINE, int start_pos, int line_number, std::string const& filename)
{
	/*
	 *  syntax :
	 *    #garrison : some_unique_id
	 *      unit1_name : number_of_units
	 *      unit2_name : number_of_units
	 *      unit3_name : number_of_units
	 *      ...
	 *
	 *    #garrison : some_other_id
	 *      unit1_name : number_of_units
	 *      unit2_name : number_of_units
	 *      unit3_name : number_of_units
	 *      ...
	 */

	std::ostream& OUT = *Simulator::get_ostream();

	std::string front, back;

	if(LINE.at(start_pos) == '#')
	{
		utils::process_line(LINE, start_pos, front, back);

		CURRENT.garrison = Garrison::create(back);
		if(CURRENT.garrison == NULL) {
			debug::ERROR(OUT, line_number, "garrison with id '" + back + "' is already defined");
			return false;
		}

		debug::INFO(OUT, line_number, "new garrison - " + back);
	}
	else 
	{
		utils::process_line(LINE, start_pos, front, back);

		if(front == "ORDER") {
			std::list<std::string> units;
			utils::extract_list_from_string(back, units);

			std::list<const Unit*> order;
			std::string error;

			std::list<std::string>::const_iterator cit = units.begin();
			for(; cit != units.end(); ++cit)
			{
				const Unit* unit = Unit::find(*cit, error);
				if(unit == NULL) {
					debug::ERROR(OUT, line_number, error);
					return false;
				}

				order.push_back(unit);
			}

			if(!CURRENT.garrison->set_unit_order(order, error))
			{
				debug::ERROR(OUT, line_number, error);
				return false;
			}

		}
		else {
		
			std::string error;
			const Unit* unit = Unit::find(front, error);
			if(unit == NULL) {
				debug::ERROR(OUT, line_number, error);
				return false;
			}

			std::list<int> number;
			int num = utils::extract_list_from_string(back, number);
			if(num != 1) {
				debug::ERROR(OUT, line_number, "expected number of units, got '" + back + "'");
				return false;
			}

			CURRENT.garrison->add_units(unit, number.front());
		}
	}

	return true;
}

bool SimParser::process_sim(std::string const& LINE, int start_pos, int line_number, std::string const& filename)
{
	std::ostream& OUT = *Simulator::get_ostream();

	if(LINE.at(start_pos) == '#')
	{
		std::string front, back;
		utils::process_line(LINE, start_pos, front, back);

		std::list<std::string> opponents;
		int num = utils::extract_list_from_string(back, opponents); 
		if(num != 2) {
			debug::ERROR(OUT, line_number, "expected two opponent garrisons");
			return false;
		}

		Garrison *g1 = Garrison::get(opponents.front());
		if(g1 == NULL) {
			debug::ERROR(OUT, line_number, "undefined garrison '" + opponents.front() + "'");
			return false;
		}

		Garrison *g2 = Garrison::get(opponents.back());
		if(g2 == NULL) {
			debug::ERROR(OUT, line_number, "undefined garrison '" + opponents.back() + "'");
			return false;
		}

		Garrison &opp1 = *g1;
		Garrison &opp2 = *g2;

		debug::INFO(OUT, line_number, "Simulating combat between '" + opponents.front() + "' and '" + opponents.back() + "'");

		Simulator* sim = new Simulator();

		sim->set_garrisons(&opp1, &opp2);
		set_current_sim(sim);
	}
	else
	{
		std::string front, back;
		utils::process_line(LINE, start_pos, front, back);

		if(front == "repeats") {
			std::list<int> rep;
			int num = utils::extract_list_from_string(back, rep);
			if(num != 1) {
				debug::ERROR(OUT, line_number, "unexpected repeats value - " + back);
				return false;
			}
			CURRENT.sim->set_repeats( rep.front() );
		}
		else if(front == "modify") {
			std::list<int> mod;
			utils::extract_list_from_string(back, mod);
			if(mod.size() != 2) {
				debug::ERROR(OUT, line_number, "unexpected two values - " + back);
				return false;
			}

			CURRENT.sim->set_modify(mod.front() != 0, mod.back() != 0);
		}
		else if(front == "results") {
			std::list<std::string> results;
			int num = utils::extract_list_from_string(back, results);
			if(num == 0) {
				debug::WARNING(OUT, line_number, "empty list");
				debug::WARNING(OUT, line_number, "  in file '" + filename + "'");
				return true;
			}

			bool stats = false, log = false, minmax = false;
			std::list<std::string>::iterator iter = results.begin();
			for(; iter != results.end(); ++iter)
			{
				if     (*iter == "STATS" ) stats  = true;
				else if(*iter == "MINMAX") minmax = true;
				else if(*iter == "LOG"   ) log    = true;
				else {
					debug::ERROR(OUT, line_number, "unknown results argument - " + *iter);
					return false;
				}
			}

			CURRENT.sim->set_print_results(stats, minmax, log);
		}
		else {
			debug::ERROR(OUT, line_number, "unknown argument (" + front + ") during simulator processing");
			return false;
		}

	}
	return true;
}

void SimParser::set_current_sim(Simulator* sim)
{
	if(sim == CURRENT.sim){
		// do nothing
		return;
	}

	if(CURRENT.sim != NULL)
	{
		CURRENT.sim->start_combat();
		delete CURRENT.sim;
	}

	CURRENT.sim = sim;
}

